Skip to content

Optimize decompression (on ARM) up to 2x - #64

Draft
Dandandan wants to merge 41 commits into
BurntSushi:masterfrom
Dandandan:snappy_perf
Draft

Optimize decompression (on ARM) up to 2x#64
Dandandan wants to merge 41 commits into
BurntSushi:masterfrom
Dandandan:snappy_perf

Conversation

@Dandandan

@Dandandan Dandandan commented Mar 11, 2026

Copy link
Copy Markdown

Some claude-assisted optimizations.

On M1 CPU:

group                              master                                 perf
-----                              ------                                 ----
snap/decompress/uflat00_html       1.73     24.1±1.01µs     4.0 GB/sec    1.00     14.0±1.67µs     6.8 GB/sec
snap/decompress/uflat01_urls       1.57   405.3±31.75µs  1652.0 MB/sec    1.00    257.6±5.83µs     2.5 GB/sec
snap/decompress/uflat02_jpg        1.23      3.4±0.92µs    34.2 GB/sec    1.00      2.7±0.73µs    42.0 GB/sec
snap/decompress/uflat03_jpg_200    1.00     61.0±0.36ns     3.1 GB/sec    1.05     64.0±3.34ns     2.9 GB/sec
snap/decompress/uflat04_pdf        1.40      4.4±0.53µs    21.8 GB/sec    1.00      3.1±0.67µs    30.5 GB/sec
snap/decompress/uflat05_html4      2.00    129.8±2.21µs     2.9 GB/sec    1.00     65.0±5.18µs     5.9 GB/sec
snap/decompress/uflat06_txt1       1.60    120.3±1.42µs  1205.9 MB/sec    1.00     75.4±2.00µs  1924.5 MB/sec
snap/decompress/uflat07_txt2       1.55    105.4±1.29µs  1132.2 MB/sec    1.00     68.1±4.44µs  1752.8 MB/sec
snap/decompress/uflat08_txt3       1.48    334.2±3.90µs  1217.7 MB/sec    1.00   226.4±46.81µs  1797.5 MB/sec
snap/decompress/uflat09_txt4       1.55   472.9±23.41µs   971.7 MB/sec    1.00    305.5±4.66µs  1504.1 MB/sec
snap/decompress/uflat10_pb         1.60     20.8±0.54µs     5.3 GB/sec    1.00     12.9±2.12µs     8.5 GB/sec
snap/decompress/uflat11_gaviota    1.86    122.0±3.98µs  1440.6 MB/sec    1.00     65.5±2.72µs     2.6 GB/sec

@Dandandan Dandandan changed the title [draft] Optimize decompression Optimize decompression (on ARM) Mar 12, 2026
@Dandandan Dandandan changed the title Optimize decompression (on ARM) Optimize decompression (on ARM) up to 2x Mar 13, 2026
@Dandandan

Dandandan commented Mar 13, 2026

Copy link
Copy Markdown
Author

@BurntSushi I am wondering, what is your current capacity in reviewing/taking a change like this (when cleaned up)?

In arrow-rs we depend on snap but I saw it has some subpar performance (~2x worse on decompressing parquet string columns) on ARM CPUs compared to Snappy C++.

Comment thread src/decompress.rs
Comment on lines +89 to +90
ptr::copy_nonoverlapping(srcp, dst, 8);
ptr::copy_nonoverlapping(srcp.add(8), dst.add(8), 8);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

During my testing, I noticed that the implementation of this part on ARM differs from that on x86. Is there a better implementation?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, this was something that resulted in better codegen (according to claude), but I didn't run the benchmarks on x86.

I think for some parts it seemed a bit harder to generate great code for both with one implementation.

I think it probably would make sense to take some ideas from this PR and port them individually in order to improve the performance.

Comment thread src/decompress.rs
let src_len = self.src.len();
let dst_len = self.dst.len();

if src_len < 17 || dst_len < 88 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two numbers seem a bit odd. Could you please provide further clarification? My understanding is as follows:
17: src ≥ 17 bytes: 1-byte tag + up to 16 bytes of literal data; no need to check the remaining space in src within the loop
88: dst ≥ 88 bytes: 64-byte maximum copy + 24 bytes of overwrite margin; no need to check the remaining space in dst within the loop

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - it is a bit magic created by vibe code ;) but it is headroom for the rest of the loop to avoid checking space.

Comment thread src/decompress.rs
Comment on lines +47 to +58
fn extract_offset_mask(tag_type: usize) -> u32 {
#[cfg(target_arch = "aarch64")]
{
const MASKS_PACKED: u64 = 0x0000FFFF00FF0000u64;
((MASKS_PACKED >> (tag_type * 16)) & 0xFFFF) as u32
}
#[cfg(not(target_arch = "aarch64"))]
{
const MASKS: [u32; 4] = [0, 0xFF, 0xFFFF, 0];
MASKS[tag_type]
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would using bitwise operations throughout affect X86 performance?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it did generate worse code (but didn't run benchmarks)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants